SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
10.1 KB · 198 lines tsx
Raw Blame History
1import { ExternalLink } from 'lucide-react';2import type { Metadata } from 'next';3import Link from 'next/link';4import { notFound } from 'next/navigation';5import { CompareTrayBar } from '@/components/compare/compare-tray-bar';6import { ModelsTable } from '@/components/entity/blocks';7import { Tri } from '@/components/models/badges';8import { LICENCE_DIMENSIONS, OpennessBlock } from '@/components/models/openness-block';9import { EntityBadge } from '@/components/ui/badges';10import { Hint } from '@/components/ui/hint';11import { Pagination } from '@/components/ui/pagination';12import { Container, Note } from '@/components/ui/section';13import { ApiError, apiD1, safe } from '@/lib/api';14import { fmtInt } from '@/lib/format';15import { SITE_NAME, SITE_URL } from '@/lib/site';16import type { LicenseDetail, ModelOpenness } from '@/lib/types';1718type Params = { params: Promise<{ key: string }>; searchParams: Promise<{ offset?: string }> };19const LIMIT = 50;20export const revalidate = 3600;2122async function load(key: string, offset: number): Promise<LicenseDetail> {23  try {24    return await apiD1.license(key, LIMIT, offset);25  } catch (e) {26    if (e instanceof ApiError && e.notFound) notFound();27    throw e;28  }29}3031const tri = (v: boolean | null) => (v === true ? 'yes' : v === false ? 'no' : 'unknown');3233export async function generateMetadata({ params }: Params): Promise<Metadata> {34  const { key } = await params;35  const l = await safe(apiD1.license(key, 1));36  if (!l) return { title: 'Licence', robots: { index: false } };37  const title = `${l.label} — Model licence terms & models using it`;38  const description = `${l.label} (${l.category}${l.spdx ? `, SPDX ${l.spdx}` : ''}): commercial use ${tri(l.commercial_use)}, redistribution ${tri(l.redistribution)}, derivatives ${tri(l.derivatives)}, hosting restrictions ${tri(l.hosting_restrictions)}, acceptable-use policy ${tri(l.acceptable_use)}. ${fmtInt(l.models.total)} canonical models use it. ${SITE_NAME}.`.slice(0, 300);39  const canonical = `/licenses/${encodeURIComponent(l.key)}`;40  return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'article' } };41}4243/** Openness view derived from the licence permissions alone (weights downloadable · commercial · redistribution · derivatives). */44function opennessFromLicence(l: LicenseDetail): ModelOpenness {45  const category = !l.weights_downloadable ? 'proprietary' : l.commercial_use === false || l.hosting_restrictions === true || l.derivatives === false || l.category === 'community' || l.category === 'research-only' || l.category === 'responsible-ai' ? 'restricted-weights' : 'open-weights';46  const labels: Record<string, string> = { proprietary: 'Closed / proprietary', 'restricted-weights': 'Restricted weights', 'open-weights': 'Open weights' };47  const defs: Record<string, string> = {48    proprietary: 'Weights are not redistributable under this licence.',49    'restricted-weights': 'Weights downloadable, but the licence restricts commercial use, hosting, derivatives or field of use.',50    'open-weights': 'Weights downloadable under a permissive licence allowing commercial use; code or data may be missing.',51  };52  return {53    category,54    raw: l.key,55    label: labels[category] ?? category,56    definition: defs[category] ?? '',57    dimensions: { weights_available: l.weights_downloadable, source_code_available: null, training_code_available: null, training_data_disclosed: null, dataset_available: null, commercial_use_allowed: l.commercial_use, redistribution_allowed: l.redistribution, derivatives_allowed: l.derivatives },58    note: 'Derived from the licence text alone — a model under this licence may still publish code or data (see its page).',59  };60}6162export default async function LicensePage({ params, searchParams }: Params) {63  const { key } = await params;64  const { offset: off } = await searchParams;65  const offset = Math.max(0, Number(off) || 0);66  const l = await load(key, offset);67  const canonical = `/licenses/${encodeURIComponent(l.key)}`;68  const ld = { '@context': 'https://schema.org', '@type': 'CreativeWork', name: l.label, url: `${SITE_URL}${canonical}`, alternateName: l.aliases, sameAs: l.url ?? undefined, identifier: l.spdx ?? l.key, description: `${l.category} licence · commercial use ${tri(l.commercial_use)} · redistribution ${tri(l.redistribution)} · derivatives ${tri(l.derivatives)}` };6970  return (71    <Container wide>72      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />73      <nav aria-label="Breadcrumb" className="pt-5 text-xs text-ink-3">74        <ol className="flex flex-wrap items-center gap-1.5">75          <li>76            <Link href="/" className="hover:text-ink">77              AI Atlas78            </Link>79          </li>80          <li aria-hidden>/</li>81          <li>82            <Link href="/licenses" className="hover:text-ink">83              Licences84            </Link>85          </li>86          <li aria-hidden>/</li>87          <li>88            <Link href={`/licenses?category=${encodeURIComponent(l.category)}`} className="hover:text-ink">89              {l.category}90            </Link>91          </li>92          <li aria-hidden>/</li>93          <li className="text-ink-2">{l.label}</li>94        </ol>95      </nav>96      <header className="pb-6 pt-4 md:pt-5" data-license-header>97        <div className="flex flex-wrap items-center gap-2">98          <EntityBadge type="license" />99          <span className="rounded-[3px] bg-surface-2 px-1.5 text-[11px] font-medium text-ink-2">{l.category}</span>100          {l.osi_approved && <span className="rounded-[3px] bg-positive-soft px-1.5 text-[11px] font-medium text-positive">OSI approved</span>}101          {l.spdx && <span className="mono text-xs text-ink-3">SPDX {l.spdx}</span>}102        </div>103        <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">104          <div className="min-w-0">105            <h1 className="display text-[30px] md:text-[44px]">{l.label}</h1>106            <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">107              <span className="mono text-ink">{l.key}</span>108              {l.url && (109                <a href={l.url} target="_blank" rel="noopener noreferrer" className="inline-flex max-w-full min-w-0 items-center gap-1 text-ink-3 hover:text-accent">110                  <span className="truncate">{l.url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 56)}</span> <ExternalLink className="size-3.5 shrink-0" aria-hidden />111                </a>112              )}113            </p>114          </div>115          <p className="tnum text-sm text-ink-2">116            <span className="font-semibold text-ink">{fmtInt(l.models.total)}</span> canonical models{' '}117            <Link href={`/models?license=${encodeURIComponent(l.key)}`} className="link">118              open in the terminal →119            </Link>120          </p>121        </div>122      </header>123124      <div className="grid gap-10 pb-16 lg:grid-cols-[minmax(0,1fr)_24rem]">125        <div className="min-w-0 space-y-10">126          <section>127            <p className="eyebrow mb-2">Dimensions</p>128            <dl className="kv" data-license-dimensions>129              {LICENCE_DIMENSIONS.map((d) => (130                <div key={d.key}>131                  <dt>{d.label}</dt>132                  <dd className="flex items-start justify-between gap-2 text-ink">133                    <span>134                      <Tri v={l[d.key] as boolean | null} yes={d.invert ? 'Yes — restrictions apply' : 'Yes'} no={d.invert ? 'None' : 'No'} />135                    </span>136                    <Hint text={d.hint} align="right" className="-my-1 shrink-0" />137                  </dd>138                </div>139              ))}140              <div>141                <dt>Weights downloadable</dt>142                <dd className="text-ink">143                  <Tri v={l.weights_downloadable} />144                </dd>145              </div>146              <div>147                <dt>OSI approved</dt>148                <dd className="text-ink">149                  <Tri v={l.osi_approved} />150                </dd>151              </div>152            </dl>153            <Note className="mt-2">Read from the licence text by the ontology; a dash means the text is ambiguous and nothing is assumed. Not legal advice.</Note>154          </section>155          <section>156            <p className="eyebrow mb-2">What this means for openness</p>157            <OpennessBlock openness={opennessFromLicence(l)} compact />158          </section>159          <section id="models">160            <p className="eyebrow mb-2">161              Models under this licence <span className="tnum text-ink-3">{fmtInt(l.models.total)}</span>162            </p>163            <ModelsTable items={l.models.items} />164            <Pagination total={l.models.total} limit={LIMIT} offset={offset} makeHref={(o) => `${canonical}${o ? `?offset=${o}#models` : '#models'}`} className="mt-4" />165          </section>166        </div>167        <aside className="min-w-0 space-y-8">168          {l.aliases?.length > 0 && (169            <section>170              <p className="eyebrow mb-2">171                Aliases mapped to this licence <span className="tnum text-ink-3">{fmtInt(l.aliases.length)}</span>172              </p>173              <ul className="flex flex-wrap gap-1.5 text-xs">174                {l.aliases.map((a) => (175                  <li key={a} className="mono border border-rule px-1.5 py-0.5 text-ink-2">176                    {a}177                  </li>178                ))}179              </ul>180              <Note className="mt-2">Raw labels stated by sources that the ontology maps to this canonical key.</Note>181            </section>182          )}183          <section>184            <p className="eyebrow mb-2">Category</p>185            <p className="text-sm text-ink-2">186              <Link href={`/licenses?category=${encodeURIComponent(l.category)}`} className="link">187                {l.category}188              </Link>{' '}189              — see the other licences of this category and the <Link href="/methodology" className="link">openness methodology</Link>.190            </p>191          </section>192        </aside>193      </div>194      <CompareTrayBar />195    </Container>196  );197}198